package pl.com.qapps.datastore.base;

import static com.google.appengine.api.datastore.FetchOptions.Builder.withLimit;

import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Future;

import pl.com.qapps.datastore.ApiUtils;

import com.google.appengine.api.datastore.AccessibilityHelper;
import com.google.appengine.api.datastore.DatastoreNeedIndexException;
import com.google.appengine.api.datastore.DatastoreServiceConfig;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.FetchOptions;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.QueryResultIterator;
import com.google.appengine.api.datastore.QueryResultList;
import com.google.appengine.api.datastore.Transaction;
import com.google.appengine.api.utils.FutureWrapper;
import com.google.apphosting.api.ApiProxy.ApiConfig;
import com.google.apphosting.api.DatastorePb;

/**
 * Implements PreparedQuery.
 * 
 */
class PreparedQueryImpl extends BasePreparedQuery {
	// TODO why it is not used, fix it or remove it
	private final DatastoreServiceConfig datastoreServiceConfig;
	private final ApiConfig apiConfig;
	private final Query query;
	private final Transaction txn;

	/*
	 * Class that knows how to run queries.
	 */
	private final QueryRunner runner;

	public PreparedQueryImpl(ApiConfig apiConfig,
			DatastoreServiceConfig datastoreServiceConfig, Query query,
			Transaction txn, QueryRunner queryRunner) {
		this.apiConfig = apiConfig;
		this.datastoreServiceConfig = datastoreServiceConfig;
		this.query = query;
		this.txn = txn;
		this.runner = queryRunner;

		// TODO consider getting rid of restricting only ancestor queries inside
		// transactions (a big internal change comparing to original datastore)
		if (txn != null && query.getAncestor() == null) {
			throw new IllegalArgumentException(
					"Only ancestor queries are allowed inside transactions.");
		}
		ApiUtils.ensureTxnActive(txn);
	}

	@Override
	public List<Entity> asList(FetchOptions fetchOptions) {
		return new LazyList(runQuery(query, fetchOptions));
	}

	@Override
	public QueryResultList<Entity> asQueryResultList(FetchOptions fetchOptions) {
		FetchOptions override = AccessibilityHelper
				.newFetchOptions(fetchOptions);
		if (AccessibilityHelper.getCompile(override) == null) {
			AccessibilityHelper.compile(override, true);
		}
		LazyList lazyList = new LazyList(runQuery(query, override));
		return lazyList;
	}

	@Override
	public Iterator<Entity> asIterator(FetchOptions fetchOptions) {
		return runQuery(query, fetchOptions);
	}

	@Override
	public QueryResultIterator<Entity> asQueryResultIterator(
			FetchOptions fetchOptions) {
		if (AccessibilityHelper.getCompile(fetchOptions) == null) {
			fetchOptions = AccessibilityHelper.compile(
					AccessibilityHelper.newFetchOptions(fetchOptions), true);
		}
		return runQuery(query, fetchOptions);
	}

	@Override
	public Entity asSingleEntity() throws TooManyResultsException {
		List<Entity> entities = asList(withLimit(2));
		if (entities.isEmpty()) {
			return null;
		} else if (entities.size() != 1) {
			throw new TooManyResultsException();
		}
		return entities.get(0);
	}

	@Override
	/**
	 * Counts the number of entities in the result set.
	 *
	 * This method will run a query that will offset past all results and return
	 * the number of results that have been skipped in the process.
	 *
	 * (offset, limit) is converted to (offset + limit, 0) so that no results are
	 * actually returned. The resulting count is max(0, skipped - offset). This
	 * is the number of entities in the range [offset, offset + limit) which is
	 * the count.
	 */
	public int countEntities(FetchOptions fetchOptions) {
		FetchOptions overrideOptions = AccessibilityHelper
				.newFetchOptions(fetchOptions);

		overrideOptions.limit(0);
		if (fetchOptions.getLimit() != null) {
			if (fetchOptions.getOffset() != null) {
				int offset = fetchOptions.getLimit() + fetchOptions.getOffset();
				overrideOptions
						.offset(offset >= 0 ? offset : Integer.MAX_VALUE);
			} else {
				overrideOptions.offset(fetchOptions.getLimit());
			}
		} else {
			overrideOptions.offset(Integer.MAX_VALUE);
		}

		int count = runQuery(query, overrideOptions).getNumSkipped();
		if (fetchOptions.getOffset() != null) {
			if (count < fetchOptions.getOffset()) {
				count = 0;
			} else {
				count = count - fetchOptions.getOffset();
			}
		}
		return count;
	}

	// TODO !!!! i think there is a big fucking mistake
	private QueryResultIteratorImpl runQuery(Query q, FetchOptions fetchOptions) {
		// throw new UnsupportedOperationException("not supported yet");
		// final DatastorePb.Query queryProto = convertToPb(q, fetchOptions);
		// if (datastoreServiceConfig.getReadPolicy().getConsistency() ==
		// Consistency.EVENTUAL) {
		// queryProto
		// .setFailoverMs(AccessibilityHelper.ARBITRARY_FAILOVER_READ_MS);
		// queryProto.setStrong(false);
		// }
		//
		Future<QueryResult> result = runner.runQuery(q, txn, fetchOptions);

		result = new FutureWrapper<QueryResult, QueryResult>(result) {
			@Override
			protected Throwable convertException(Throwable cause) {
				// if (cause instanceof DatastoreNeedIndexException) {
				// // addMissingIndexData(queryProto,
				// // (DatastoreNeedIndexException) cause);
				// }
				return cause;
			}

			@Override
			protected QueryResult wrap(QueryResult result) throws Exception {
				System.out.println("wrapping QueryResult from query");
				return result;
			}
		};

		QueryResultsSourceImpl src = new QueryResultsSourceImpl(apiConfig,
				fetchOptions, txn, result);
		return new QueryResultIteratorImpl(this, src, fetchOptions, txn);
	}

	private void addMissingIndexData(DatastorePb.Query queryProto,
			DatastoreNeedIndexException e) {
		// TODO implement missing index data
		throw new UnsupportedOperationException("not supported yet");
		// IndexComponentsOnlyQuery indexQuery = new IndexComponentsOnlyQuery(
		// queryProto);
		// CompositeIndexManager mgr = new CompositeIndexManager();
		// OnestoreEntity.Index index = mgr.compositeIndexForQuery(indexQuery);
		// if (index != null) {
		// String xml = mgr.generateXmlForIndex(index, IndexSource.manual);
		// e.setMissingIndexDefinitionXml(xml);
		// } else {
		// }
	}

	@Deprecated
	private DatastorePb.Query convertToPb(Query q, FetchOptions fetchOptions) {
		throw new UnsupportedOperationException("not supported yet");
		// DatastorePb.Query queryProto = QueryTranslator.convertToPb(q,
		// fetchOptions);
		// if (txn != null) {
		// TransactionImpl.ensureTxnActive(txn);
		// queryProto.setTransaction(BaseDatastoreServiceImpl
		// .localTxnToRemoteTxn(txn));
		// }
		// return queryProto;
	}

	@Override
	public String toString() {
		return query.toString() + (txn != null ? " IN " + txn : "");
	}
}
